use std::path::Path;

use bc_base::{
    BlockId,
    block::{Block, BlockInDb, BlockIndexNode},
};

use crate::BlockDataStorageError;
use crate::inner_storage::DbHashMap;
use crate::inner_storage::{block::BlockStorage, block_index::BlockIndexStorage};

#[derive(Debug)]
pub struct BlockAndIndexStorage {
    block_storage: BlockStorage,
    block_index_storage: BlockIndexStorage,
}

impl BlockAndIndexStorage {
    pub fn new(db_root_dir: &Path) -> Result<Self, bc_db::DBError> {
        Ok(Self {
            block_storage: BlockStorage::new(db_root_dir)?,
            block_index_storage: BlockIndexStorage::new(db_root_dir)?,
        })
    }

    #[cfg(debug_assertions)]
    pub async fn get_item_number(&self) -> usize {
        let n1 = self.block_index_storage.get_item_number().await;
        #[cfg(debug_assertions)]
        {
            let n2 = self.block_storage.get_item_number().await;
            debug_assert_eq!(n1, n2);
        }
        n1
    }

    pub async fn load_block_index_to_cache(
        &self,
        block_index_cache: &mut DbHashMap<BlockId, BlockIndexNode>,
    ) -> Result<(), BlockDataStorageError> {
        *block_index_cache = self.block_index_storage.get_all_data().await;
        Ok(())
    }

    pub async fn flush_block_and_index_db(&self) -> Result<(), BlockDataStorageError> {
        self.block_storage.flush_database().await?;
        self.block_index_storage.flush_database().await?;
        Ok(())
    }

    pub async fn write_block_and_index_to_db(
        &self,
        block: &Block,
    ) -> Result<(), BlockDataStorageError> {
        let index_node = BlockIndexNode::new_by_block(block);
        let block_id = index_node.get_block_id();
        self.block_index_storage.put(block_id, &index_node).await?;

        let blk_in_db = BlockInDb::new(block);
        self.block_storage.put(block_id, &blk_in_db).await?;

        Ok(())
    }
}

// for block
impl BlockAndIndexStorage {
    pub async fn block_exists_in_db(&self, block_id: &BlockId) -> bool {
        let t1 = self.block_storage.exists(block_id).await;
        #[cfg(debug_assertions)]
        {
            let t2 = self.block_index_storage.exists(block_id).await;
            debug_assert_eq!(t1, t2);
        }

        t1
    }

    pub async fn read_block_from_db(
        &self,
        block_id: &BlockId,
    ) -> Result<BlockInDb, BlockDataStorageError> {
        let r = self.block_storage.get(block_id).await?;
        match r {
            Some(r) => Ok(r),
            None => Err(BlockDataStorageError::BlockNotFound(*block_id)),
        }
    }

    pub async fn write_block_to_db(
        &self,
        block_id: &BlockId,
        block: &BlockInDb,
    ) -> Result<(), BlockDataStorageError> {
        self.block_storage.put(block_id, block).await?;
        Ok(())
    }

    pub async fn delete_block_from_db(
        &self,
        block_id: &BlockId,
    ) -> Result<(), BlockDataStorageError> {
        self.block_storage.erase(block_id).await?;
        Ok(())
    }
}

// for block index
impl BlockAndIndexStorage {
    pub async fn block_index_exists_in_db(&self, block_id: &BlockId) -> bool {
        self.block_index_storage.exists(block_id).await
    }

    pub async fn read_block_index_from_db(
        &self,
        block_id: &BlockId,
    ) -> Result<BlockIndexNode, BlockDataStorageError> {
        let r = self.block_index_storage.get(block_id).await?;
        match r {
            Some(r) => Ok(r),
            None => Err(BlockDataStorageError::BlockIndexNotFound(*block_id)),
        }
    }

    pub async fn write_block_index_to_db(
        &mut self,
        block_id: &BlockId,
        index_node: &BlockIndexNode,
    ) -> Result<(), BlockDataStorageError> {
        self.block_index_storage.put(block_id, index_node).await?;
        Ok(())
    }

    pub async fn delete_block_index_from_db(
        &mut self,
        block_id: &BlockId,
    ) -> Result<(), BlockDataStorageError> {
        self.block_index_storage.erase(block_id).await?;
        Ok(())
    }
}
